package cn.chmcyz.bootredis.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisLockController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 并发情况下存在超卖问题
     * @return
     */
    @GetMapping("/reduce")
    public String reduce(){
        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            System.out.println("库存充足，扣减成功,剩余"+(stock-1));
            stringRedisTemplate.opsForValue().decrement("stock");
        }else {
            System.out.println("库存不足");
        }
        return "ok";

    }

    /**
     * 解决并发情况下的超卖问题，原理是redis的 setnx命令
     *
     * @return
     */
    @GetMapping("/lockReduce")
    public String lockReduce() throws InterruptedException {
        String clientId = UUID.randomUUID().toString().replace("-", "");
      try{
          //setnx stocklock 1 获取锁 设置过期时间，防止死锁,具体时间根据业务确定
          if (Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent("lockkey", clientId,10, TimeUnit.SECONDS))) {
              Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
              if (stock > 0) {
                  System.out.println("库存充足，扣减成功,剩余"+(stock-1));
                  Thread.sleep(1);
                  stringRedisTemplate.opsForValue().decrement("stock");
              }else {
                  System.out.println("库存不足");
              }
              return "ok";
          }else {
              System.out.println("获取库存失败");
              return "系统繁忙";
          }
      }finally {
          //确保释放锁
          /**
           *  确保释放锁只能是加锁的线程，防止的情况如下
           * 1.线程A进来加锁
           * 2.10s后，线程A业务没有执行完，释放锁
           * 3.线程B进来加锁成功
           * 4.线程A执行完，释放锁
           * 5.此时锁是线程B加的，却被线程A释放
           */
          if (clientId.equals(stringRedisTemplate.opsForValue().get("lockkey"))) {
              stringRedisTemplate.delete("lockkey");
          }

      }
    }

    /**
     * redisson分布式锁
     */
    @GetMapping("/redissonReduce")
    public String redissonReduce() {
        RLock lock = redissonClient.getLock("redissonkey");
        //加锁
        lock.lock(10,TimeUnit.SECONDS);
        try{
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                System.out.println("库存充足，扣减成功,剩余"+(stock-1));
                stringRedisTemplate.opsForValue().decrement("stock");
            }else {
                System.out.println("库存不足");
            }
            return "ok";
        }finally {
            //确保释放锁
            lock.unlock();

        }
    }

    /**
     * redisson分布式锁优化
     * 采用分段式锁，例如把 100 个库存，设置5个key，每个key20个库存
     */
    @GetMapping("/segmentReduce")
    public String segmentReduce() {
        int random = new Random().nextInt(5);
        RLock lock = redissonClient.getLock("segmentReduce:"+random);
        //加锁
        lock.lock(10,TimeUnit.SECONDS);
        try{
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock:"+random));
            if (stock > 0) {
                System.out.println(random+"库存充足，扣减成功,剩余"+(stock-1));
                stringRedisTemplate.opsForValue().decrement("stock:"+random);
            }else {
                System.out.println(random+"库存不足");
            }
            return "ok";
        }finally {
            //确保释放锁
            lock.unlock();

        }
    }

}
